library(data.table)
library(ggplot2)
library(caret)
library(nnet)
library(pls)
library(FactoMineR)
library(qqman)
library(mixOmics)
import des datas
data(Phenotype)
Warning: data set ‘Phenotype’ not found
data(Genomic)
Warning: data set ‘Genomic’ not found
data("localisation")
Warning: data set ‘localisation’ not found
pheno <- as.data.table(Phenotype)
loc_df<- as.data.table(localisation)
geno<- as.data.table(Genomic)
geno[, ID := rownames(Genomic)]
pheno[, ID := rownames(Phenotype)]
merged<- merge(pheno, loc_df[,list(Population,ID)], by = "ID")
#merged<- merge(merged, geno, by = "ID")
LM (Pheno ~ population )
df <- as.data.table(merged)
phenos <- names(df)[sapply(df, is.numeric)]
phenos <- setdiff(phenos, c("class_index","Lat","Lgn","ID"))
phenos
[1] "HT2009" "CIRC2009" "HT2011" "CIRC2011" "H"
[6] "G" "S" "H_G" "S_G" "Klason_lignin"
[11] "Glucose" "Xyl_Glu" "C5_C6" "Wet_chem_extractives" "Dia2015_sqrt"
[16] "InfraDens" "Rust" "BrAnglVert" "RamifSyllep" "BudFlushSlope"
get_r2 <- function(var) {
form<- as.formula(paste(var, "~ Population"))
mod<- lm(form, data = df)
summary(mod)$r.squared
}
r2_values<- data.table(
phenotype = phenos,
r2= sapply(phenos, get_r2)
)
ggplot(r2_values, aes(x = reorder(phenotype, r2), y = r2)) +
geom_point(size = 3) +
geom_segment(aes(x = phenotype, xend = phenotype, y = 0, yend = r2)) +
coord_flip() +
ylab("R² lm(Phénotype ~ Population)") +
xlab("Caractère phénotypique") +
theme_minimal()

khi 2 snp ~ pop
table cont snp chr13 et pop => haplo pour différentier la pop
Data visualisation
Groupes de variables : chromosomes, phénotypes, populations
constructions des axes : chromosomes => on sépare les individus
selon leur distances génomiques et on projette en supplémentaires les
phénotypes / population
on pourra voir quels chromosomes ne sont pas dutout corrélés avec des
traits phénotypiques
Réccupération des positions :
merged<- merge(merged, geno, by = "ID")
geno_mat <- as.matrix(geno)
coords <- do.call(rbind, strsplit(colnames(geno), "_"))
Warning: number of columns of result is not a multiple of vector length (arg 1)
chrom <- as.factor(coords[,1])
pos <- as.numeric(coords[,2])
Warning: NAs introduced by coercion
PCA
pca <- PCA(merged[,2:217022], quali.sup=21 ,quanti.sup=(2:20) )


plot(pca,choix="ind",habillage=21)

plot(pca,choix="var", #invisible = c("quali","var"),
label = c("quanti.sup"),
select = "coord 10" )
MixOmic
sPCA
X<- as.matrix(merged[,23:217022])
X2 <- X[, apply(X, 2, sd) != 0]
Y <- as.matrix(merged$CIRC2011)
result.spca.multi <- spca(X2, keepX = c(50, 30))
plotIndiv(result.spca.multi)

plotVar(result.spca.multi)
Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
Please use tidy evaluation idioms with `aes()`.
See also `vignette("ggplot2-in-packages")` for more information.

# extract the variables used to construct the first PC
selectVar(result.spca.multi, comp = 1)$name
[1] "Chr02_6422161" "Chr02_6427114" "Chr02_6427264" "Chr02_6416381" "Chr13_15625954" "Chr13_15574007"
[7] "Chr13_15627568" "Chr07_7181966" "Chr13_15626544" "Chr02_6382504" "Chr13_15635566" "Chr02_6422861"
[13] "Chr02_6423252" "Chr02_6426926" "Chr12_3267410" "Chr12_3264433" "Chr12_3267439" "Chr12_3264479"
[19] "Chr10_20127228" "Chr01_42022246" "Chr17_7247760" "Chr10_20121818" "Chr10_20126974" "Chr17_5836683"
[25] "Chr02_6325961" "Chr03_18516527" "Chr06_22417885" "Chr02_6332720" "Chr11_17347389" "Chr06_24641715"
[31] "Chr11_17347361" "Chr14_13405721" "Chr11_17347960" "Chr01_14722117" "Chr06_24641246" "Chr03_11418460"
[37] "Chr03_11422148" "Chr01_14722377" "Chr03_15711505" "Chr11_4789295" "Chr01_14717384" "Chr15_9806504"
[43] "Chr17_14258124" "Chr17_14265429" "Chr07_12991228" "Chr07_12991382" "Chr01_8617466" "Chr06_20423555"
[49] "Chr07_12997322" "Chr01_14708394"
# depict weight assigned to each of these variables
plotLoadings(result.spca.multi, method = 'mean', contrib = 'max')

block PLS
pls.result <- mixOmics::pls(X, Y)
Warning: the standard deviation is zero
plotIndiv(pls.result)

plotIndiv(pls.result, group = merged$Population,
rep.space = 'XY-variate',
ellipse = TRUE, # plot using the ellipses
legend = TRUE)

block sPLS
head(merged[,2:21])
Y <- as.matrix(merged[,2:21])
spls.result <- mixOmics::spls(X2, Y, ncomp=5, keepX = c(rep(1000,5)))
plotIndiv(spls.result, group = merged$Population,
rep.space = 'XY-variate',
ellipse = TRUE, # plot using the ellipses
legend = TRUE)

# Identifier les coefficients non nuls
nonzero_idx <- loadX != 0
Error: object 'loadX' not found
manhattan plot
manhattan(merged_nonzero, chr = "1", bp = "2", p = "loading",snp = "SNP",
col = c("blue4", "orange3"),
cex = 0.6) # taille des points
block PLS_DA
# use the mirna, mrna and protein expression levels as predictive datasets
# note that each dataset is measured across the same individuals (samples)
X1<- as.matrix(merged[,23:217022])
X1 <- X1[, apply(X1, 2, sd) != 0]
X2 <- as.matrix(merged[,2:21])
Y <- as.factor(merged$Population)
X <- list(geno = X1, pheno = X2)
result.diablo.tcga <- block.plsda(X, Y) # run the method
plotIndiv(result.diablo.tcga,
rep.space = 'XY-variate',
ellipse = TRUE, # plot using the ellipses
legend = TRUE)

Block sPLS DA
# set the number of features to use for the X datasets
list.keepX = list(geno = c(rep(10000,5)), pheno = c(rep(20,5)))
# run the method
result.sparse.diablo.tcga <- block.splsda(X, Y, keepX = list.keepX, ncomp = 5)
plotLoadings(result.sparse.diablo.tcga, ncomp = 5)

Chr13_15574007 ressort encore
plotIndiv(result.sparse.diablo.tcga,ellipse = TRUE,legend = TRUE)

plotVar(result.sparse.diablo.tcga) # plot the variables

Boostrapped sPLS
library(spls)
y <- merged$CIRC2009
y <- as.numeric(y)
X <- as.matrix(merged[,23:217022])
# Split stratifié
# Ici, on fait un split simple car phénotype continu
train_index <- createDataPartition(y, p = 0.9, list = FALSE)
X_train <- X[train_index, ]
var_threshold <- 1e-8
nonzero_cols <- apply(X_train, 2, var) > var_threshold
length(nonzero_cols)
X_train <- X_train[, nonzero_cols]
X_test <- X[-train_index, ]
X_test <- X_test[, nonzero_cols]
y_train <- y[train_index]
y_test <- y[-train_index]
eta = seq(0.1,0.9,0.1)
K = c(1:5)
cv.spls( X_train, y_train, fold=5, K, eta, kappa=0.5, select="pls2", fit="simpls",scale.x=FALSE, scale.y=FALSE, plot.it=TRUE )
model <- spls(X_train, y_train, K = 4, eta =0.2 , kappa=0.5, select="pls2", fit="simpls",
scale.x=FALSE, scale.y=FALSE, eps=1e-4, maxstep=100, trace=FALSE)
ped <- predict.spls( model, X_test, type = c("fit","coefficient"))
ped
f <- spls::ci.spls( model, coverage=0.95, B=1000,
plot.it=TRUE, plot.fix="y",
plot.var=NA, K=model$K, fit=model$fit )
names(f)
nonzero_idx <- which(f$betahat != 0)
betahat_nonzero <- f$betahat[nonzero_idx]
length(nonzero_idx)
MFA
res = MFA(geno_mat, group=c(25974,16646,12284,11790,14769,17004,8327,14040,9840,15964,7483,7403,8040,11123,8486,7629,6747,8267,4876,308), type=c(rep("s",20)), ncp=5, name.group=c("Chr01","Chr02","Chr03","Chr04", "Chr05","Chr06","Chr07","Chr08","Chr09","Chr10", "Chr11","Chr12","Chr13", "Chr14","Chr15","Chr16","Chr17","Chr18","Chr19", "scaffold"))
dim(geno_mat)
PLS : Phenotype ~ all SNP
inner_train_index <- createDataPartition(y_train, p = 0.9, list = FALSE)
X_train <- X_train_df[inner_train_index, , drop = FALSE]
y_train <- y_train[inner_train_index]
X_test <- X_train_df[-inner_train_index, , drop = FALSE]
y_test <- y_train[-inner_train_index]
df_train <- data.frame(y = y_train, X_train_df)
pls_final <- plsr(y ~ ., data = df_train, ncomp = ncomp_opt, validation = "none")
# Évaluation sur le fold externe
y_pred_final <- as.vector(predict(pls_final, newdata = X_test_df, ncomp = ncomp_opt))
PLS Subset SNP | calcul de VIP | nested CV
\[VIP_{j} = \sqrt{ p \frac{\displaystyle
\sum_{h=1}^{A} SS_{Y,h}\frac{w_{jh}^{2}}{\lVert w_{h}
\rVert^{2}}}{\displaystyle \sum_{h=1}^{A} SS_{Y,h}} }\]
set.seed(123)
# Paramètres
n_iter <- 400
subset_size <- 10000 # nombre de SNP aléatoires par itération
ncomp_candidates <- 1:5 # candidats pour ncomp
nfold_outer <- 5
nfold_inner <- 10
results_perf
vip_perf<- results_perf[, .(R2_test_mean = mean(R2_test)), by = iter]
vip_perf_fold<- results_perf[, .(R2_test_mean = mean(R2_test)), by = fold]
ggplot(results_perf[1:1000,], aes(x = factor(iter), y = R2_test)) +
geom_violin(fill = "skyblue", color = "black") +
labs(x = "Itération", y = expression(R^2), title = "R² 10-outer-fold ") +
theme_minimal()
mean(vip_perf_fold$R2_test_mean)
vip_perf_fold
library(data.table)
library(ggplot2)
# Calcul du VIP moyen par SNP
vip_mean<- results_vip[, .(VIP_mean = mean(VIP),SNP_count = .N), by = SNP]
# Sélection des 30 SNP ayant le VIP moyen le plus élevé
topN<- vip_mean[order(-VIP_mean)][0:5000, SNP]
df_topNPLS<- results_vip[SNP %in% topN]
df_topNPLS <- merge(df_topNPLS, vip_mean[, .(SNP, SNP_count)], by = "SNP")
df_topNPLS$SNP_count <- df_topNPLS$SNP_count/10
ggplot(df_topNPLS, aes(x = reorder(SNP,VIP), y = VIP, fill = SNP_count)) +
geom_violin(trim = FALSE) +
geom_boxplot(width = 0.1, outlier.size = 0.5) +
scale_fill_gradient(low = "lightblue", high = "darkblue") +
theme_bw() +
theme(
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)
) +
labs(
x = "SNP",
y = "VIP",
title = "Distribution du VIP pour les n = 50 SNP les plus importants",
fill = "Nbr de selection du SNP"
)
hist(vip_mean$VIP_mean, breaks = seq(min(vip_mean$VIP_mean), max(vip_mean$VIP_mean), length.out = 50),
main = "Distribution des scores VIP moyens",
xlab = "scores VIP moyens", col = "lightblue", border = "white")
Lasso
library(glmnet)
# Assurer que y est numérique et X est une matrice
y <- as.numeric(pheno$CIRC2009)
X <- as.matrix(geno)
train_index <- createDataPartition(y, p = 0.8, list = FALSE)
X_train <- X[train_index, ]
X_test <- X[-train_index, ]
y_train <- y[train_index]
y_test <- y[-train_index]
# Ajustement du Lasso avec validation croisée pour choisir lambda
set.seed(123)
cv_lasso <- cv.glmnet(X_train, y_train, alpha = 1, nfolds = 5)
# Afficher la meilleure valeur de lambda
best_lambda <- cv_lasso$lambda.min
print(best_lambda)
# Tracer l'erreur de validation croisée
plot(cv_lasso)
# Ajuster le modèle final avec lambda optimal
lasso_model <- glmnet(X_train, y_train, alpha = 1, lambda = best_lambda)
# Extraire les coefficients
coef(lasso_model)
print(best_lambda)
y_pred <- predict(lasso_model, newx = X_test)
# Calcul du R²
SSE <- sum((y_test - y_pred)^2) # somme des carrés des erreurs
SST <- sum((y_test - mean(y))^2) # somme totale des carrés
R2 <- 1 - SSE/SST
print(sqrt(SSE))
R2
coef_df <- data.frame(
SNP = rownames(coef(lasso_model)),
Coefficient = as.numeric(coef(lasso_model))
)
coef_nonzero <- coef_df[coef_df$Coefficient != 0, ]
coef_nonzero_dt <- as.data.table(coef_nonzero)
# Top N SNP par valeur absolue des coefficients
N <- 50
coef_nonzero_dt[, abs_coef := abs(Coefficient)]
topN_Lasso <- coef_nonzero_dt[order(-abs_coef)][1:N, .(SNP, Coefficient)]
topN_Lasso
Comparaison selection Lasso et PLS
common_snps <- intersect(df_topNPLS$SNP, coef_nonzero$SNP)
# SNP communs
common_snps
length(common_snps)
réegression ridge adaptative
y <- as.numeric(pheno$CIRC2009)
X <- as.matrix(geno)
# Split externe train/test
set.seed(123)
train_index <- createDataPartition(y, p = 0.9, list = FALSE)
X_train <- X[train_index, ]
X_test <- X[-train_index, ]
y_train <- y[train_index]
y_test <- y[-train_index]
# Split interne du train en D1 et D2 : 50/50
set.seed(123)
idx_D1 <- createDataPartition(y_train, p = 0.5, list = FALSE)
X_D1 <- X_train[idx_D1, ]
X_D2 <- X_train[-idx_D1, ]
y_D1 <- y_train[idx_D1]
y_D2 <- y_train[-idx_D1]
STEP I : SCREENING (Elastic Net sur D1)
alpha <- 1 # Elastic Net si alpha = 0.5, Lasso si = 1, ridge si =0
set.seed(123)
cv_enet <- cv.glmnet(
X_D1, y_D1,
alpha = alpha,
nfolds = 10,
standardize = TRUE
)
lambda_hat <- cv_enet$lambda.min
lambda_hat
# Ajustement final
enet_model <- glmnet(X_D1, y_D1, alpha = alpha, lambda = 0.002)
coefs <- coef(enet_model)
selected <- which(coefs[-1] != 0) # indices des SNP sélectionnés
S_hat <- selected
cat("Nombre de variables sélectionnées (screening):", length(S_hat), "\n")
# Poids dérivés des coefficients Elastic Net
beta_hat <- as.numeric(coefs[-1])
weights <- abs(beta_hat)
weights <- weights[S_hat]
weights <- weights / max(weights) # normalisation
lambda_hat <- 0.002
STEP II : CLEANING (Ridge sur D2)
X_D2_sel <- X_D2[, S_hat, drop = FALSE]
# Pondération Ridge : mettre plus de pénalité sur les petites β_EN
ridge_penalty <- 1 / (weights + 1e-6)
set.seed(123)
cv_ridge <- cv.glmnet(
X_D2_sel, y_D2,
alpha = 0, # alpha = 0 → Ridge
nfolds = 10,
penalty.factor = ridge_penalty
)
lambda_ridge <- cv_ridge$lambda.min
# Ajustement final sur D2
ridge_model <- glmnet(
X_D2_sel, y_D2,
alpha = 0,
lambda = lambda_ridge,
penalty.factor = ridge_penalty
)
# Coefficients ridge finaux (sur D2)
beta_clean <- coef(ridge_model)
STEP III : REFIT FINAL SUR D = D1 ∪ D2 (train complet)
X_train_sel <- X_train[, S_hat, drop = FALSE]
ridge_final <- glmnet(
X_train_sel, y_train,
alpha = 0,
lambda = lambda_ridge,
penalty.factor = ridge_penalty
)
# Prédictions sur le test externe
X_test_sel <- X_test[, S_hat, drop = FALSE]
y_pred <- predict(ridge_final, newx = X_test_sel)
# R² externe
R2_test <- 1 - sum((y_test - y_pred)^2) / sum((y_test - mean(y_test))^2)
cat("R2 externe :", R2_test, "\n")
LS0tCnRpdGxlOiAiQ2x1c3RlcmluZyBMRCBwaGVub3R5cGlxdWUgYmFzZWQiCmZvcm1hdDogaHRtbApvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKZWRpdG9yOiB2aXN1YWwKLS0tCgpgYGB7cn0KbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkobm5ldCkKbGlicmFyeShwbHMpCmxpYnJhcnkoRmFjdG9NaW5lUikKbGlicmFyeShxcW1hbikKbGlicmFyeShtaXhPbWljcykKCmBgYAoKIyBpbXBvcnQgZGVzIGRhdGFzCgpgYGB7cn0KZGF0YShQaGVub3R5cGUpCmRhdGEoR2Vub21pYykKZGF0YSgibG9jYWxpc2F0aW9uIikKYGBgCgpgYGB7cn0KcGhlbm8gPC0gYXMuZGF0YS50YWJsZShQaGVub3R5cGUpCmxvY19kZjwtIGFzLmRhdGEudGFibGUobG9jYWxpc2F0aW9uKQpnZW5vPC0gYXMuZGF0YS50YWJsZShHZW5vbWljKQpnZW5vWywgSUQgOj0gcm93bmFtZXMoR2Vub21pYyldCnBoZW5vWywgSUQgOj0gcm93bmFtZXMoUGhlbm90eXBlKV0KCm1lcmdlZDwtIG1lcmdlKHBoZW5vLCBsb2NfZGZbLGxpc3QoUG9wdWxhdGlvbixJRCldLCBieSA9ICJJRCIpCgpgYGAKCiMgTE0gKFBoZW5vIFx+IHBvcHVsYXRpb24gKQoKYGBge3J9CmRmIDwtIGFzLmRhdGEudGFibGUobWVyZ2VkKQpgYGAKCgoKYGBge3J9CnBoZW5vcyA8LSBuYW1lcyhkZilbc2FwcGx5KGRmLCBpcy5udW1lcmljKV0KcGhlbm9zIDwtIHNldGRpZmYocGhlbm9zLCBjKCJjbGFzc19pbmRleCIsIkxhdCIsIkxnbiIsIklEIikpCmBgYAoKYGBge3J9CnBoZW5vcwpgYGAKCgpgYGB7cn0KZ2V0X3IyIDwtIGZ1bmN0aW9uKHZhcikgewogIGZvcm08LSBhcy5mb3JtdWxhKHBhc3RlKHZhciwgIn4gUG9wdWxhdGlvbiIpKQogIG1vZDwtIGxtKGZvcm0sIGRhdGEgPSBkZikKICBzdW1tYXJ5KG1vZCkkci5zcXVhcmVkCn0KCnIyX3ZhbHVlczwtIGRhdGEudGFibGUoCiAgcGhlbm90eXBlID0gcGhlbm9zLAogIHIyPSBzYXBwbHkocGhlbm9zLCBnZXRfcjIpKQpgYGAKCmBgYHtyfQoKZ2dwbG90KHIyX3ZhbHVlcywgYWVzKHggPSByZW9yZGVyKHBoZW5vdHlwZSwgcjIpLCB5ID0gcjIpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMykgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IHBoZW5vdHlwZSwgeGVuZCA9IHBoZW5vdHlwZSwgeSA9IDAsIHllbmQgPSByMikpICsKICBjb29yZF9mbGlwKCkgKwogIHlsYWIoIlLCsiBsbShQaMOpbm90eXBlIH4gUG9wdWxhdGlvbikiKSArCiAgeGxhYigiQ2FyYWN0w6hyZSBwaMOpbm90eXBpcXVlIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKa2hpIDIgc25wIH4gcG9wIAoKdGFibGUgY29udCBzbnAgY2hyMTMgZXQgcG9wID0+IGhhcGxvIHBvdXIgZGlmZsOpcmVudGllciBsYSBwb3AgCgojIERhdGEgdmlzdWFsaXNhdGlvbgoKR3JvdXBlcyBkZSB2YXJpYWJsZXMgOiBjaHJvbW9zb21lcywgcGjDqW5vdHlwZXMsIHBvcHVsYXRpb25zCgpjb25zdHJ1Y3Rpb25zIGRlcyBheGVzIDogY2hyb21vc29tZXMgPVw+IG9uIHPDqXBhcmUgbGVzIGluZGl2aWR1cyBzZWxvbiBsZXVyIGRpc3RhbmNlcyBnw6lub21pcXVlcyBldCBvbiBwcm9qZXR0ZSBlbiBzdXBwbMOpbWVudGFpcmVzIGxlcyBwaMOpbm90eXBlcyAvIHBvcHVsYXRpb24KCm9uIHBvdXJyYSB2b2lyIHF1ZWxzIGNocm9tb3NvbWVzIG5lIHNvbnQgcGFzIGR1dG91dCBjb3Jyw6lsw6lzIGF2ZWMgZGVzIHRyYWl0cyBwaMOpbm90eXBpcXVlcwoKIyBSw6ljY3Vww6lyYXRpb24gZGVzIHBvc2l0aW9ucyA6CgpgYGB7cn0KbWVyZ2VkPC0gbWVyZ2UobWVyZ2VkLCBnZW5vLCBieSA9ICJJRCIpCmdlbm9fbWF0IDwtIGFzLm1hdHJpeChnZW5vKQpjb29yZHMgPC0gZG8uY2FsbChyYmluZCwgc3Ryc3BsaXQoY29sbmFtZXMoZ2VubyksICJfIikpCmNocm9tIDwtIGFzLmZhY3Rvcihjb29yZHNbLDFdKQpwb3MgPC0gYXMubnVtZXJpYyhjb29yZHNbLDJdKQpgYGAKCiMjIFBDQQoKYGBge3J9CnBjYSA8LSBQQ0EobWVyZ2VkWywyOjIxNzAyMl0sIHF1YWxpLnN1cD0yMSAscXVhbnRpLnN1cD0oMjoyMCkgKQpgYGAKCmBgYHtyfQpwbG90KHBjYSxjaG9peD0iaW5kIixoYWJpbGxhZ2U9MjEpCmBgYAoKYGBge3J9CnBsb3QocGNhLGNob2l4PSJ2YXIiLCAjaW52aXNpYmxlID0gYygicXVhbGkiLCJ2YXIiKSwKICAgICBsYWJlbCA9IGMoInF1YW50aS5zdXAiKSwKICAgICBzZWxlY3QgPSAiY29vcmQgMTAiICkKYGBgCgojIyBNaXhPbWljCgojIyMgc1BDQQoKYGBge3J9Clg8LSBhcy5tYXRyaXgobWVyZ2VkWywyMzoyMTcwMjJdKQpYMiA8LSBYWywgYXBwbHkoWCwgMiwgc2QpICE9IDBdCgpZIDwtIGFzLm1hdHJpeChtZXJnZWQkQ0lSQzIwMTEpCgpyZXN1bHQuc3BjYS5tdWx0aSA8LSBzcGNhKFgyLCBrZWVwWCA9IGMoNTAsIDMwKSkKCnBsb3RJbmRpdihyZXN1bHQuc3BjYS5tdWx0aSkgIApwbG90VmFyKHJlc3VsdC5zcGNhLm11bHRpKSAgIAoKIyBleHRyYWN0IHRoZSB2YXJpYWJsZXMgdXNlZCB0byBjb25zdHJ1Y3QgdGhlIGZpcnN0IFBDCnNlbGVjdFZhcihyZXN1bHQuc3BjYS5tdWx0aSwgY29tcCA9IDEpJG5hbWUgCiMgZGVwaWN0IHdlaWdodCBhc3NpZ25lZCB0byBlYWNoIG9mIHRoZXNlIHZhcmlhYmxlcwpwbG90TG9hZGluZ3MocmVzdWx0LnNwY2EubXVsdGksIG1ldGhvZCA9ICdtZWFuJywgY29udHJpYiA9ICdtYXgnKQpgYGAKCiMjIyBibG9jayBQTFMKCmBgYHtyfQpwbHMucmVzdWx0IDwtIG1peE9taWNzOjpwbHMoWCwgWSkgCnBsb3RJbmRpdihwbHMucmVzdWx0KSAKYGBgCgpgYGB7cn0KcGxvdEluZGl2KHBscy5yZXN1bHQsIGdyb3VwID0gbWVyZ2VkJFBvcHVsYXRpb24sIAogICAgICAgICAgcmVwLnNwYWNlID0gJ1hZLXZhcmlhdGUnLCAKICAgICAgICAgIGVsbGlwc2UgPSBUUlVFLCAgIyBwbG90IHVzaW5nIHRoZSBlbGxpcHNlcwogICAgICAgICAgbGVnZW5kID0gVFJVRSkKYGBgCgojIyMgYmxvY2sgc1BMUwoKYGBge3J9CmhlYWQobWVyZ2VkWywyOjIxXSkKYGBgCgpgYGB7cn0KWSA8LSBhcy5tYXRyaXgobWVyZ2VkWywyOjIxXSkKCnNwbHMucmVzdWx0IDwtIG1peE9taWNzOjpzcGxzKFgyLCBZLCBuY29tcD01LCBrZWVwWCA9IGMocmVwKDEwMDAsNSkpKSAKCnBsb3RJbmRpdihzcGxzLnJlc3VsdCwgZ3JvdXAgPSBtZXJnZWQkUG9wdWxhdGlvbiwgCiAgICAgICAgICByZXAuc3BhY2UgPSAnWFktdmFyaWF0ZScsIAogICAgICAgICAgZWxsaXBzZSA9IFRSVUUsICAjIHBsb3QgdXNpbmcgdGhlIGVsbGlwc2VzCiAgICAgICAgICBsZWdlbmQgPSBUUlVFKQpgYGAKCmBgYHtyfQojIElkZW50aWZpZXIgbGVzIGNvZWZmaWNpZW50cyBub24gbnVscwpub256ZXJvX2lkeCA8LSBsb2FkWCAhPSAwCmRmX25vbnplcm9fWCA8LSBkYXRhLmZyYW1lKAogIFNOUCA9IG5hbWVzKGxvYWRYKVtub256ZXJvX2lkeF0sCiAgbG9hZGluZyAgPSBsb2FkWFtub256ZXJvX2lkeF0KKQpjb29yZHMgPC0gZG8uY2FsbChyYmluZCwgc3Ryc3BsaXQocm93bmFtZXMoZGZfbm9uemVyb19YKSwgIl8iKSkKY29vcmRzWywxXSA8LSBhcy5udW1lcmljKHN1YigiQ2hyIiwgIiIsIGNvb3Jkc1ssMV0pKQpwb3MgPC0gYXMubnVtZXJpYyhjb29yZHNbLDJdKQptZXJnZWRfbm9uemVybyA8LSBjYmluZChjb29yZHMsZGZfbm9uemVyb19YKQptZXJnZWRfbm9uemVybyQiMiIgPC0gYXMubnVtZXJpYyhtZXJnZWRfbm9uemVybyQiMiIpCm1lcmdlZF9ub256ZXJvJCIxIiA8LSBhcy5udW1lcmljKG1lcmdlZF9ub256ZXJvJCIxIikKbWVyZ2VkX25vbnplcm8kbG9hZGluZyA8LSBhYnMobWVyZ2VkX25vbnplcm8kbG9hZGluZykKYGBgCgojIG1hbmhhdHRhbiBwbG90CgpgYGB7cn0KbWFuaGF0dGFuKG1lcmdlZF9ub256ZXJvLCBjaHIgPSAiMSIsIGJwID0gIjIiLCBwID0gImxvYWRpbmciLHNucCA9ICJTTlAiLAogICAgICAgICAgY29sID0gYygiYmx1ZTQiLCAib3JhbmdlMyIpLCAKICAgICAgICAgIGNleCA9IDAuNikgICMgdGFpbGxlIGRlcyBwb2ludHMKYGBgCgojIyMgYmxvY2sgUExTX0RBCgpgYGB7cn0KIyB1c2UgdGhlIG1pcm5hLCBtcm5hIGFuZCBwcm90ZWluIGV4cHJlc3Npb24gbGV2ZWxzIGFzIHByZWRpY3RpdmUgZGF0YXNldHMKIyBub3RlIHRoYXQgZWFjaCBkYXRhc2V0IGlzIG1lYXN1cmVkIGFjcm9zcyB0aGUgc2FtZSBpbmRpdmlkdWFscyAoc2FtcGxlcykKClgxPC0gYXMubWF0cml4KG1lcmdlZFssMjM6MjE3MDIyXSkKWDEgPC0gWDFbLCBhcHBseShYMSwgMiwgc2QpICE9IDBdClgyIDwtIGFzLm1hdHJpeChtZXJnZWRbLDI6MjFdKQoKWSA8LSBhcy5mYWN0b3IobWVyZ2VkJFBvcHVsYXRpb24pCgpYIDwtIGxpc3QoZ2VubyA9IFgxLCBwaGVubyA9IFgyKQoKYGBgCgpgYGB7cn0KcmVzdWx0LmRpYWJsby50Y2dhIDwtIGJsb2NrLnBsc2RhKFgsIFkpICMgcnVuIHRoZSBtZXRob2QKCnBsb3RJbmRpdihyZXN1bHQuZGlhYmxvLnRjZ2EsCiAgICAgICAgICByZXAuc3BhY2UgPSAnWFktdmFyaWF0ZScsIAogICAgICAgICAgZWxsaXBzZSA9IFRSVUUsICAjIHBsb3QgdXNpbmcgdGhlIGVsbGlwc2VzCiAgICAgICAgICBsZWdlbmQgPSBUUlVFKQpgYGAKCiMjIyBCbG9jayBzUExTIERBCgpgYGB7cn0KIyBzZXQgdGhlIG51bWJlciBvZiBmZWF0dXJlcyB0byB1c2UgZm9yIHRoZSBYIGRhdGFzZXRzCmxpc3Qua2VlcFggPSBsaXN0KGdlbm8gPSBjKHJlcCgxMDAwMCw1KSksIHBoZW5vID0gYyhyZXAoMjAsNSkpKQojIHJ1biB0aGUgbWV0aG9kCnJlc3VsdC5zcGFyc2UuZGlhYmxvLnRjZ2EgPC0gIGJsb2NrLnNwbHNkYShYLCBZLCBrZWVwWCA9IGxpc3Qua2VlcFgsIG5jb21wID0gNSkKYGBgCgpgYGB7cn0KcGxvdExvYWRpbmdzKHJlc3VsdC5zcGFyc2UuZGlhYmxvLnRjZ2EsIG5jb21wID0gNSkKYGBgCgpDaHIxM18xNTU3NDAwNyByZXNzb3J0IGVuY29yZQoKYGBge3J9CnBsb3RJbmRpdihyZXN1bHQuc3BhcnNlLmRpYWJsby50Y2dhLGVsbGlwc2UgPSBUUlVFLGxlZ2VuZCA9IFRSVUUpIApgYGAKCmBgYHtyfQpwbG90VmFyKHJlc3VsdC5zcGFyc2UuZGlhYmxvLnRjZ2EpICMgcGxvdCB0aGUgdmFyaWFibGVzCmBgYAoKIyMjIEJvb3N0cmFwcGVkIHNQTFMKCmBgYHtyfQpsaWJyYXJ5KHNwbHMpCnkgPC0gbWVyZ2VkJENJUkMyMDA5ICAKeSA8LSBhcy5udW1lcmljKHkpClggPC0gYXMubWF0cml4KG1lcmdlZFssMjM6MjE3MDIyXSkKYGBgCgpgYGB7cn0KIyBTcGxpdCBzdHJhdGlmacOpIAojIEljaSwgb24gZmFpdCB1biBzcGxpdCBzaW1wbGUgY2FyIHBow6lub3R5cGUgY29udGludQp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHksIHAgPSAwLjksIGxpc3QgPSBGQUxTRSkKClhfdHJhaW4gPC0gWFt0cmFpbl9pbmRleCwgXQp2YXJfdGhyZXNob2xkIDwtIDFlLTgKCm5vbnplcm9fY29scyA8LSBhcHBseShYX3RyYWluLCAyLCB2YXIpID4gdmFyX3RocmVzaG9sZAoKbGVuZ3RoKG5vbnplcm9fY29scykKWF90cmFpbiA8LSBYX3RyYWluWywgbm9uemVyb19jb2xzXQoKWF90ZXN0ICA8LSBYWy10cmFpbl9pbmRleCwgXQpYX3Rlc3QgIDwtIFhfdGVzdFssIG5vbnplcm9fY29sc10KCgp5X3RyYWluIDwtIHlbdHJhaW5faW5kZXhdCgp5X3Rlc3QgIDwtIHlbLXRyYWluX2luZGV4XQpgYGAKCmBgYHtyfQpldGEgPSBzZXEoMC4xLDAuOSwwLjEpCksgPSBjKDE6NSkKY3Yuc3BscyggWF90cmFpbiwgeV90cmFpbiwgZm9sZD01LCBLLCBldGEsIGthcHBhPTAuNSwgc2VsZWN0PSJwbHMyIiwgZml0PSJzaW1wbHMiLHNjYWxlLng9RkFMU0UsIHNjYWxlLnk9RkFMU0UsIHBsb3QuaXQ9VFJVRSApCmBgYAoKYGBge3J9Cm1vZGVsIDwtIHNwbHMoWF90cmFpbiwgeV90cmFpbiwgSyA9IDQsIGV0YSA9MC4yICwga2FwcGE9MC41LCBzZWxlY3Q9InBsczIiLCBmaXQ9InNpbXBscyIsCnNjYWxlLng9RkFMU0UsIHNjYWxlLnk9RkFMU0UsIGVwcz0xZS00LCBtYXhzdGVwPTEwMCwgdHJhY2U9RkFMU0UpCmBgYAoKYGBge3J9CnBlZCA8LSBwcmVkaWN0LnNwbHMoIG1vZGVsLCBYX3Rlc3QsIHR5cGUgPSBjKCJmaXQiLCJjb2VmZmljaWVudCIpKQpgYGAKCmBgYHtyfQpwZWQKYGBgCgpgYGB7cn0KZiA8LSBzcGxzOjpjaS5zcGxzKCBtb2RlbCwgY292ZXJhZ2U9MC45NSwgQj0xMDAwLAogICAgICAgICAgICAgICAgICAgIHBsb3QuaXQ9VFJVRSwgcGxvdC5maXg9InkiLAogICAgICAgICAgICAgICAgICAgIHBsb3QudmFyPU5BLCBLPW1vZGVsJEssIGZpdD1tb2RlbCRmaXQgKQoKCmBgYAoKYGBge3J9Cm5hbWVzKGYpCmBgYAoKYGBge3J9Cm5vbnplcm9faWR4IDwtIHdoaWNoKGYkYmV0YWhhdCAhPSAwKQpiZXRhaGF0X25vbnplcm8gPC0gZiRiZXRhaGF0W25vbnplcm9faWR4XQpgYGAKCmBgYHtyfQpsZW5ndGgobm9uemVyb19pZHgpCmBgYAoKIyMgTUZBCgpgYGB7cn0KcmVzID0gTUZBKGdlbm9fbWF0LCBncm91cD1jKDI1OTc0LDE2NjQ2LDEyMjg0LDExNzkwLDE0NzY5LDE3MDA0LDgzMjcsMTQwNDAsOTg0MCwxNTk2NCw3NDgzLDc0MDMsODA0MCwxMTEyMyw4NDg2LDc2MjksNjc0Nyw4MjY3LDQ4NzYsMzA4KSwgdHlwZT1jKHJlcCgicyIsMjApKSwgbmNwPTUsIG5hbWUuZ3JvdXA9YygiQ2hyMDEiLCJDaHIwMiIsIkNocjAzIiwiQ2hyMDQiLCAiQ2hyMDUiLCJDaHIwNiIsIkNocjA3IiwiQ2hyMDgiLCJDaHIwOSIsIkNocjEwIiwgIkNocjExIiwiQ2hyMTIiLCJDaHIxMyIsICJDaHIxNCIsIkNocjE1IiwiQ2hyMTYiLCJDaHIxNyIsIkNocjE4IiwiQ2hyMTkiLCAic2NhZmZvbGQiKSkKYGBgCgpgYGB7cn0KZGltKGdlbm9fbWF0KQpgYGAKCiMgUExTIDogUGhlbm90eXBlIFx+IGFsbCBTTlAKCmBgYHtyfQppbm5lcl90cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHlfdHJhaW4sIHAgPSAwLjksIGxpc3QgPSBGQUxTRSkKWF90cmFpbiA8LSBYX3RyYWluX2RmW2lubmVyX3RyYWluX2luZGV4LCAsIGRyb3AgPSBGQUxTRV0KeV90cmFpbiA8LSB5X3RyYWluW2lubmVyX3RyYWluX2luZGV4XQpYX3Rlc3QgPC0gWF90cmFpbl9kZlstaW5uZXJfdHJhaW5faW5kZXgsICwgZHJvcCA9IEZBTFNFXQp5X3Rlc3QgPC0geV90cmFpblstaW5uZXJfdHJhaW5faW5kZXhdCmBgYAoKCmBgYHtyfQpkZl90cmFpbiA8LSBkYXRhLmZyYW1lKHkgPSB5X3RyYWluLCBYX3RyYWluX2RmKQpwbHNfZmluYWwgPC0gcGxzcih5IH4gLiwgZGF0YSA9IGRmX3RyYWluLCBuY29tcCA9IG5jb21wX29wdCwgdmFsaWRhdGlvbiA9ICJub25lIikKICAKICAjIMOJdmFsdWF0aW9uIHN1ciBsZSBmb2xkIGV4dGVybmUgCiAgCnlfcHJlZF9maW5hbCA8LSBhcy52ZWN0b3IocHJlZGljdChwbHNfZmluYWwsIG5ld2RhdGEgPSBYX3Rlc3RfZGYsIG5jb21wID0gbmNvbXBfb3B0KSkKYGBgCgoKIyBQTFMgU3Vic2V0IFNOUCBcfCBjYWxjdWwgZGUgVklQIFx8IG5lc3RlZCBDVgoKJCRWSVBfe2p9ID0gXHNxcnR7IHAgXGZyYWN7XGRpc3BsYXlzdHlsZSBcc3VtX3toPTF9XntBfSBTU197WSxofVxmcmFje3dfe2pofV57Mn19e1xsVmVydCB3X3tofSBcclZlcnReezJ9fX17XGRpc3BsYXlzdHlsZSBcc3VtX3toPTF9XntBfSBTU197WSxofX0gfSQkCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQojIFBhcmFtw6h0cmVzCm5faXRlciA8LSA0MDAKc3Vic2V0X3NpemUgPC0gMTAwMDAgICAgICMgbm9tYnJlIGRlIFNOUCBhbMOpYXRvaXJlcyBwYXIgaXTDqXJhdGlvbgpuY29tcF9jYW5kaWRhdGVzIDwtIDE6NSAgIyBjYW5kaWRhdHMgcG91ciBuY29tcApuZm9sZF9vdXRlciA8LSA1Cm5mb2xkX2lubmVyIDwtIDEwCmBgYAoKYGBge3J9CnJlc3VsdHNfcGVyZgp2aXBfcGVyZjwtIHJlc3VsdHNfcGVyZlssIC4oUjJfdGVzdF9tZWFuID0gbWVhbihSMl90ZXN0KSksIGJ5ID0gaXRlcl0KdmlwX3BlcmZfZm9sZDwtIHJlc3VsdHNfcGVyZlssIC4oUjJfdGVzdF9tZWFuID0gbWVhbihSMl90ZXN0KSksIGJ5ID0gZm9sZF0KYGBgCgpgYGB7cn0KZ2dwbG90KHJlc3VsdHNfcGVyZlsxOjEwMDAsXSwgYWVzKHggPSBmYWN0b3IoaXRlciksIHkgPSBSMl90ZXN0KSkgKwogIGdlb21fdmlvbGluKGZpbGwgPSAic2t5Ymx1ZSIsIGNvbG9yID0gImJsYWNrIikgKwogIGxhYnMoeCA9ICJJdMOpcmF0aW9uIiwgeSA9IGV4cHJlc3Npb24oUl4yKSwgdGl0bGUgPSAiUsKyIDEwLW91dGVyLWZvbGQgIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCmBgYHtyfQptZWFuKHZpcF9wZXJmX2ZvbGQkUjJfdGVzdF9tZWFuKQpgYGAKCmBgYHtyfQp2aXBfcGVyZl9mb2xkCmBgYAoKYGBge3J9CmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShnZ3Bsb3QyKQoKCiMgQ2FsY3VsIGR1IFZJUCBtb3llbiBwYXIgU05QCnZpcF9tZWFuPC0gcmVzdWx0c192aXBbLCAuKFZJUF9tZWFuID0gbWVhbihWSVApLFNOUF9jb3VudCA9IC5OKSwgYnkgPSBTTlBdCmBgYAoKYGBge3J9CiMgU8OpbGVjdGlvbiBkZXMgMzAgU05QIGF5YW50IGxlIFZJUCBtb3llbiBsZSBwbHVzIMOpbGV2w6kKdG9wTjwtIHZpcF9tZWFuW29yZGVyKC1WSVBfbWVhbildWzA6NTAwMCwgU05QXQoKZGZfdG9wTlBMUzwtIHJlc3VsdHNfdmlwW1NOUCAlaW4lIHRvcE5dCmRmX3RvcE5QTFMgPC0gbWVyZ2UoZGZfdG9wTlBMUywgdmlwX21lYW5bLCAuKFNOUCwgU05QX2NvdW50KV0sIGJ5ID0gIlNOUCIpCmRmX3RvcE5QTFMkU05QX2NvdW50IDwtIGRmX3RvcE5QTFMkU05QX2NvdW50LzEwCgpgYGAKCmBgYHtyfQpnZ3Bsb3QoZGZfdG9wTlBMUywgYWVzKHggPSByZW9yZGVyKFNOUCxWSVApLCB5ID0gVklQLCBmaWxsID0gU05QX2NvdW50KSkgKwogIGdlb21fdmlvbGluKHRyaW0gPSBGQUxTRSkgKwogIGdlb21fYm94cGxvdCh3aWR0aCA9IDAuMSwgb3V0bGllci5zaXplID0gMC41KSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAibGlnaHRibHVlIiwgaGlnaCA9ICJkYXJrYmx1ZSIpICsKICB0aGVtZV9idygpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdCA9IDEpCiAgKSArCiAgbGFicygKICAgIHggPSAiU05QIiwKICAgIHkgPSAiVklQIiwKICAgIHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBkdSBWSVAgcG91ciBsZXMgbiA9IDUwIFNOUCBsZXMgcGx1cyBpbXBvcnRhbnRzIiwKICAgIGZpbGwgPSAiTmJyIGRlIHNlbGVjdGlvbiBkdSBTTlAiCiAgKQpgYGAKCmBgYHtyfQpoaXN0KHZpcF9tZWFuJFZJUF9tZWFuLCBicmVha3MgPSBzZXEobWluKHZpcF9tZWFuJFZJUF9tZWFuKSwgbWF4KHZpcF9tZWFuJFZJUF9tZWFuKSwgbGVuZ3RoLm91dCA9IDUwKSwKICAgICBtYWluID0gIkRpc3RyaWJ1dGlvbiBkZXMgc2NvcmVzIFZJUCBtb3llbnMiLAogICAgIHhsYWIgPSAic2NvcmVzIFZJUCBtb3llbnMiLCBjb2wgPSAibGlnaHRibHVlIiwgYm9yZGVyID0gIndoaXRlIikKYGBgCgojIExhc3NvCgpgYGB7cn0KbGlicmFyeShnbG1uZXQpCgojIEFzc3VyZXIgcXVlIHkgZXN0IG51bcOpcmlxdWUgZXQgWCBlc3QgdW5lIG1hdHJpY2UKeSA8LSBhcy5udW1lcmljKHBoZW5vJENJUkMyMDA5KQpYIDwtIGFzLm1hdHJpeChnZW5vKQoKdHJhaW5faW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih5LCBwID0gMC44LCBsaXN0ID0gRkFMU0UpClhfdHJhaW4gPC0gWFt0cmFpbl9pbmRleCwgXQpYX3Rlc3QgIDwtIFhbLXRyYWluX2luZGV4LCBdCnlfdHJhaW4gPC0geVt0cmFpbl9pbmRleF0KeV90ZXN0ICA8LSB5Wy10cmFpbl9pbmRleF0KCiMgQWp1c3RlbWVudCBkdSBMYXNzbyBhdmVjIHZhbGlkYXRpb24gY3JvaXPDqWUgcG91ciBjaG9pc2lyIGxhbWJkYQpzZXQuc2VlZCgxMjMpCgpjdl9sYXNzbyA8LSBjdi5nbG1uZXQoWF90cmFpbiwgeV90cmFpbiwgYWxwaGEgPSAxLCBuZm9sZHMgPSA1KQoKIyBBZmZpY2hlciBsYSBtZWlsbGV1cmUgdmFsZXVyIGRlIGxhbWJkYQpiZXN0X2xhbWJkYSA8LSBjdl9sYXNzbyRsYW1iZGEubWluCnByaW50KGJlc3RfbGFtYmRhKQoKIyBUcmFjZXIgbCdlcnJldXIgZGUgdmFsaWRhdGlvbiBjcm9pc8OpZQpwbG90KGN2X2xhc3NvKQoKIyBBanVzdGVyIGxlIG1vZMOobGUgZmluYWwgYXZlYyBsYW1iZGEgb3B0aW1hbApsYXNzb19tb2RlbCA8LSBnbG1uZXQoWF90cmFpbiwgeV90cmFpbiwgYWxwaGEgPSAxLCBsYW1iZGEgPSBiZXN0X2xhbWJkYSkKCiMgRXh0cmFpcmUgbGVzIGNvZWZmaWNpZW50cwpjb2VmKGxhc3NvX21vZGVsKQpgYGAKCmBgYHtyfQpwcmludChiZXN0X2xhbWJkYSkKYGBgCgpgYGB7cn0KeV9wcmVkIDwtIHByZWRpY3QobGFzc29fbW9kZWwsIG5ld3ggPSBYX3Rlc3QpCgojIENhbGN1bCBkdSBSwrIKU1NFIDwtIHN1bSgoeV90ZXN0IC0geV9wcmVkKV4yKSAgICAgICAgICAgIyBzb21tZSBkZXMgY2FycsOpcyBkZXMgZXJyZXVycwpTU1QgPC0gc3VtKCh5X3Rlc3QgLSBtZWFuKHkpKV4yKSAgICAgICAgICMgc29tbWUgdG90YWxlIGRlcyBjYXJyw6lzClIyIDwtIDEgLSBTU0UvU1NUCnByaW50KHNxcnQoU1NFKSkKUjIKYGBgCgpgYGB7cn0KY29lZl9kZiA8LSBkYXRhLmZyYW1lKAogIFNOUCA9IHJvd25hbWVzKGNvZWYobGFzc29fbW9kZWwpKSwKICBDb2VmZmljaWVudCA9IGFzLm51bWVyaWMoY29lZihsYXNzb19tb2RlbCkpCikKY29lZl9ub256ZXJvIDwtIGNvZWZfZGZbY29lZl9kZiRDb2VmZmljaWVudCAhPSAwLCBdCmNvZWZfbm9uemVyb19kdCA8LSBhcy5kYXRhLnRhYmxlKGNvZWZfbm9uemVybykKCiMgVG9wIE4gU05QIHBhciB2YWxldXIgYWJzb2x1ZSBkZXMgY29lZmZpY2llbnRzCk4gPC0gNTAKY29lZl9ub256ZXJvX2R0WywgYWJzX2NvZWYgOj0gYWJzKENvZWZmaWNpZW50KV0KdG9wTl9MYXNzbyA8LSBjb2VmX25vbnplcm9fZHRbb3JkZXIoLWFic19jb2VmKV1bMTpOLCAuKFNOUCwgQ29lZmZpY2llbnQpXQoKdG9wTl9MYXNzbwpgYGAKCiMgQ29tcGFyYWlzb24gc2VsZWN0aW9uIExhc3NvIGV0IFBMUwoKYGBge3J9Cgpjb21tb25fc25wcyA8LSBpbnRlcnNlY3QoZGZfdG9wTlBMUyRTTlAsIGNvZWZfbm9uemVybyRTTlApCgojIFNOUCBjb21tdW5zCmNvbW1vbl9zbnBzCmxlbmd0aChjb21tb25fc25wcykKCmBgYAoKIyByw6llZ3Jlc3Npb24gcmlkZ2UgYWRhcHRhdGl2ZQoKYGBge3J9CnkgPC0gYXMubnVtZXJpYyhwaGVubyRDSVJDMjAwOSkKWCA8LSBhcy5tYXRyaXgoZ2VubykKCiMgU3BsaXQgZXh0ZXJuZSB0cmFpbi90ZXN0CnNldC5zZWVkKDEyMykKdHJhaW5faW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih5LCBwID0gMC45LCBsaXN0ID0gRkFMU0UpClhfdHJhaW4gPC0gWFt0cmFpbl9pbmRleCwgXQpYX3Rlc3QgIDwtIFhbLXRyYWluX2luZGV4LCBdCnlfdHJhaW4gPC0geVt0cmFpbl9pbmRleF0KeV90ZXN0ICA8LSB5Wy10cmFpbl9pbmRleF0KCiMgU3BsaXQgaW50ZXJuZSBkdSB0cmFpbiBlbiBEMSBldCBEMiA6IDUwLzUwCnNldC5zZWVkKDEyMykKaWR4X0QxIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeV90cmFpbiwgcCA9IDAuNSwgbGlzdCA9IEZBTFNFKQoKWF9EMSA8LSBYX3RyYWluW2lkeF9EMSwgXQpYX0QyIDwtIFhfdHJhaW5bLWlkeF9EMSwgXQp5X0QxIDwtIHlfdHJhaW5baWR4X0QxXQp5X0QyIDwtIHlfdHJhaW5bLWlkeF9EMV0KYGBgCgojIyMgU1RFUCBJIDogU0NSRUVOSU5HIChFbGFzdGljIE5ldCBzdXIgRDEpCgpgYGB7cn0KYWxwaGEgPC0gMSAjIEVsYXN0aWMgTmV0IHNpIGFscGhhID0gMC41LCBMYXNzbyBzaSA9IDEsIHJpZGdlIHNpID0wIApzZXQuc2VlZCgxMjMpCmN2X2VuZXQgPC0gY3YuZ2xtbmV0KAogIFhfRDEsIHlfRDEsCiAgYWxwaGEgPSBhbHBoYSwgICAgICAgICAgICAgICAKICBuZm9sZHMgPSAxMCwKICBzdGFuZGFyZGl6ZSA9IFRSVUUKKQoKbGFtYmRhX2hhdCA8LSBjdl9lbmV0JGxhbWJkYS5taW4KYGBgCgpgYGB7cn0KbGFtYmRhX2hhdApgYGAKCmBgYHtyfQojIEFqdXN0ZW1lbnQgZmluYWwgCmVuZXRfbW9kZWwgPC0gZ2xtbmV0KFhfRDEsIHlfRDEsIGFscGhhID0gYWxwaGEsIGxhbWJkYSA9IDAuMDAyKQoKCmNvZWZzIDwtIGNvZWYoZW5ldF9tb2RlbCkKc2VsZWN0ZWQgPC0gd2hpY2goY29lZnNbLTFdICE9IDApICAgICAgICAgICMgaW5kaWNlcyBkZXMgU05QIHPDqWxlY3Rpb25uw6lzClNfaGF0IDwtIHNlbGVjdGVkCgpjYXQoIk5vbWJyZSBkZSB2YXJpYWJsZXMgc8OpbGVjdGlvbm7DqWVzIChzY3JlZW5pbmcpOiIsIGxlbmd0aChTX2hhdCksICJcbiIpCgojIFBvaWRzIGTDqXJpdsOpcyBkZXMgY29lZmZpY2llbnRzIEVsYXN0aWMgTmV0CmJldGFfaGF0IDwtIGFzLm51bWVyaWMoY29lZnNbLTFdKQp3ZWlnaHRzIDwtIGFicyhiZXRhX2hhdCkKd2VpZ2h0cyA8LSB3ZWlnaHRzW1NfaGF0XQp3ZWlnaHRzIDwtIHdlaWdodHMgLyBtYXgod2VpZ2h0cykgICAgICAgICAgIyBub3JtYWxpc2F0aW9uCmBgYAoKYGBge3J9CmxhbWJkYV9oYXQgPC0gMC4wMDIKYGBgCgojIyBTVEVQIElJIDogQ0xFQU5JTkcgKFJpZGdlIHN1ciBEMikKCmBgYHtyfQpYX0QyX3NlbCA8LSBYX0QyWywgU19oYXQsIGRyb3AgPSBGQUxTRV0KCiMgUG9uZMOpcmF0aW9uIFJpZGdlIDogbWV0dHJlIHBsdXMgZGUgcMOpbmFsaXTDqSBzdXIgbGVzIHBldGl0ZXMgzrJfRU4KcmlkZ2VfcGVuYWx0eSA8LSAxIC8gKHdlaWdodHMgKyAxZS02KQoKc2V0LnNlZWQoMTIzKQpjdl9yaWRnZSA8LSBjdi5nbG1uZXQoCiAgWF9EMl9zZWwsIHlfRDIsCiAgYWxwaGEgPSAwLCAgICAgICAgICAgICAgICAgICAgICMgYWxwaGEgPSAwIOKGkiBSaWRnZQogIG5mb2xkcyA9IDEwLAogIHBlbmFsdHkuZmFjdG9yID0gcmlkZ2VfcGVuYWx0eQopCgpsYW1iZGFfcmlkZ2UgPC0gY3ZfcmlkZ2UkbGFtYmRhLm1pbgoKIyBBanVzdGVtZW50IGZpbmFsIHN1ciBEMgpyaWRnZV9tb2RlbCA8LSBnbG1uZXQoCiAgWF9EMl9zZWwsIHlfRDIsCiAgYWxwaGEgPSAwLAogIGxhbWJkYSA9IGxhbWJkYV9yaWRnZSwKICBwZW5hbHR5LmZhY3RvciA9IHJpZGdlX3BlbmFsdHkKKQoKIyBDb2VmZmljaWVudHMgcmlkZ2UgZmluYXV4IChzdXIgRDIpCmJldGFfY2xlYW4gPC0gY29lZihyaWRnZV9tb2RlbCkKYGBgCgojIFNURVAgSUlJIDogUkVGSVQgRklOQUwgU1VSIEQgPSBEMSDiiKogRDIgKHRyYWluIGNvbXBsZXQpCgpgYGB7cn0KWF90cmFpbl9zZWwgPC0gWF90cmFpblssIFNfaGF0LCBkcm9wID0gRkFMU0VdCgpyaWRnZV9maW5hbCA8LSBnbG1uZXQoCiAgWF90cmFpbl9zZWwsIHlfdHJhaW4sCiAgYWxwaGEgPSAwLAogIGxhbWJkYSA9IGxhbWJkYV9yaWRnZSwKICBwZW5hbHR5LmZhY3RvciA9IHJpZGdlX3BlbmFsdHkKKQoKIyBQcsOpZGljdGlvbnMgc3VyIGxlIHRlc3QgZXh0ZXJuZQpYX3Rlc3Rfc2VsIDwtIFhfdGVzdFssIFNfaGF0LCBkcm9wID0gRkFMU0VdCnlfcHJlZCA8LSBwcmVkaWN0KHJpZGdlX2ZpbmFsLCBuZXd4ID0gWF90ZXN0X3NlbCkKCiMgUsKyIGV4dGVybmUKUjJfdGVzdCA8LSAxIC0gc3VtKCh5X3Rlc3QgLSB5X3ByZWQpXjIpIC8gc3VtKCh5X3Rlc3QgLSBtZWFuKHlfdGVzdCkpXjIpCmNhdCgiUjIgZXh0ZXJuZSA6IiwgUjJfdGVzdCwgIlxuIikKYGBgCg==